package maggames.games.seega;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Properties;

import maggames.core.GameMove;
import maggames.core.GameState;
import maggames.core.GameTurn;
import maggames.core.Piece;
import maggames.core.Player;
import maggames.core.base.BaseGameEngine;
import maggames.core.exceptions.InvalidTurnException;

import org.apache.log4j.Logger;

/**
 * Seega implemented GameEngine
 * 
 * @author BenjaminPlee
 * @version 1.0
 */
public class SeegaGameEngine extends BaseGameEngine {
	private static final Logger log = Logger.getLogger(SeegaGameEngine.class);
	private int centerX = 0;
	private int centerY = 0;
	private int BOARDMAX = 24;
	
	/* (non-Javadoc)
	 * @see maggames.core.interfaces.GameEngine#checkForWin()
	 */
	public Player checkForWin() {
		log.debug("checking for Win");
		checkInitialized("checkForWin()");
		
		if(gameState != GameState.TURNS)
			return Player.NONE;

		return Player.negate(board.playersUnderThreshold(1, false, false));
	}
	
	/* (non-Javadoc)
	 * @see maggames.core.base.BaseGameEngine#initialize(java.util.Properties)
	 */
	public void initialize(Properties props) {
		super.initialize(props);
		
		if(board.getWidth() % 2 != 1 || board.getHeight() % 2 != 1)
			throw new IllegalArgumentException("Invalid width or height.  Dimmensions must be odd");
		
		centerX = board.getWidth() / 2;
		centerY = board.getHeight() / 2;
		
		BOARDMAX = board.getWidth() * board.getHeight() - 1;
		
		gameState = GameState.SETUP;
		System.out.println("Seega Game Engine");
		currentPlayer = Player.PLAYER1;
	}

	/* (non-Javadoc)
	 * @see maggames.core.interfaces.GameEngine#checkTurn(maggames.core.GameTurn)
	 */
	public boolean checkTurn(GameTurn turn) {
		log.debug("checking turn");		
		checkInitialized("checkTurn()");
		
		try {
			if(turn == null) {
				throw new InvalidTurnException("Invalid turn object passed in, NULL.  Returning false.");
			}
			
			checkGameStateForTurn();
			checkPlayerForTurn(turn.getMover());
			
			if(gameState == GameState.SETUP) {
				checkTurn_SetupState(turn);
			} else if(gameState == GameState.TURNS) {
				checkTurn_TurnsState(turn);
			}
		} catch (InvalidTurnException e) {
			log.debug("InvalidTurnException caught: " + e.getMessage());
			turn.addProblem(e);
			return false;
		}

		return true;
	}

	/* (non-Javadoc)
	 * @see maggames.core.interfaces.GameEngine#takeTurn(maggames.core.GameTurn)
	 */
	public boolean takeTurn(GameTurn turn) {
		log.debug("attempting to take turn");		
		checkInitialized("takeTurn()");
		
		boolean valid = false;
		
		valid = checkTurn(turn);
		
		if(valid) { // take the turn
			Iterator <GameMove> i = turn.getMovesIterator();
			
			while(i.hasNext()) {
				executeMove(i.next());
			}
			
			Player winner = Player.NONE;
			
			if(gameState == GameState.TURNS) {
				checkForWin();
			}
			
			if(winner != Player.NONE) { // winner
				gameState = GameState.WIN;
				currentPlayer = winner;
			}
			else {
				if(gameState == GameState.SETUP && board.getTotalPieces() == BOARDMAX) {
					gameState = GameState.TURNS;
				}
				else {
					Player nextPlayer = Player.negate(currentPlayer);
					
					if(playerCanMove(nextPlayer)) {
						currentPlayer = nextPlayer;
					} // else keep the same player
				}
			}
		}
		
		return valid;
	}
	
	private void checkTurn_SetupState(GameTurn turn) throws InvalidTurnException {
		if(turn.getNumMoves() != 2) {
			throw new InvalidTurnException("Invalid number of moves during SETUP state.");
		}
		
		checkSetupMove(turn.getMove(0));
		checkSetupMove(turn.getMove(1));
	}
	
	private void checkSetupMove(GameMove move) throws InvalidTurnException {
		checkNotCenterSquare(move.getX2(), move.getY2());
		checkLocationOffBoard(move.getX1(), move.getY1());
		checkLocationOnBoard(move.getX2(), move.getY2());
		checkOnePiecePerTile(move);
	}
	
	private void checkTurn_TurnsState(GameTurn turn) throws InvalidTurnException {
		if(turn.getNumMoves() < 1 || turn.getNumMoves() > 5) {
			throw new InvalidTurnException("Invalid number of moves during TURNS state.");
		}
		
		checkInitialMove(turn.getMove(0), turn.getMover());
		
		if(turn.getNumMoves() > 1)
			checkAllCaptureMoves(turn);
	}
	
	private void checkInitialMove(GameMove move, Player player) throws InvalidTurnException {
		checkCorrectPlayerPiece(player, move.getPiece());
		checkCorrectPieceTypeMoved(move);
		checkMoveOnBoard(move);
		checkOnePiecePerTile(move);
		checkMoveAdjacent(move, false);
	}
	
	private void checkAllCaptureMoves(GameTurn turn) throws InvalidTurnException {
		int numMoves = turn.getNumMoves();
		HashSet<String> validMoves = findValidCaptures(turn.getMove(0), turn.getMover());
		
		for(int i = 1; i < numMoves; i++) {
			GameMove captured = turn.getMove(i);
			
			checkNotCenterSquare(captured.getX1(), captured.getY1());
			
			checkLocationOnBoard(captured.getX1(), captured.getY1());
			checkLocationOffBoard(captured.getX2(), captured.getY2());
			
			checkCorrectPieceTypeMoved(captured);
			checkPieceForPlayer(Player.negate(turn.getMover()), captured.getPiece());
			
			if(!validMoves.contains(captured.getX1() + "," + captured.getY1())) {
				throw new InvalidTurnException("");
			}
		}
	}
	
	private void checkNotCenterSquare(int x, int y) throws InvalidTurnException {
		if(x == centerX && y == centerY) {
			throw new InvalidTurnException("Invalid destination of center \"safe\" tile.");
		}
	}
	
	private HashSet<String> findValidCaptures(GameMove move, Player player) throws InvalidTurnException {
		HashSet<String> caps = new HashSet<String>();
		HashSet<String> temp = new HashSet<String>();
		boolean foundOtherEnd;
		Piece moverPiece = getPlayerPiece(player);
		Piece oposerPiece = getPlayerPiece(Player.negate(player));
		
		// check up
		if(move.getY2() > 1) {
			temp.clear();
			foundOtherEnd = false;
			
			for(int y = (move.getY2() - 1); y >= 0; y--) {
				if(board.getTile(move.getX2(), y) == moverPiece) {
					foundOtherEnd = true;
					break;
				}
				else if(board.getTile(move.getX2(), y) == oposerPiece) {
					temp.add(move.getX1() + "," + move.getY1());
				}
				else {
					break;
				}
			}
			
			if(foundOtherEnd) {
				caps.addAll(temp);
			}
		}
		
		// check down
		if(move.getY2() < board.getHeight() - 1) {
			temp.clear();
			foundOtherEnd = false;
			
			for(int y = (move.getY2() + 1); y < board.getHeight(); y++) {
				if(board.getTile(move.getX2(), y) == moverPiece) {
					foundOtherEnd = true;
					break;
				}
				else if(board.getTile(move.getX2(), y) == oposerPiece) {
					temp.add(move.getX1() + "," + move.getY1());
				}
				else {
					break;
				}
			}
			
			if(foundOtherEnd) {
				caps.addAll(temp);
			}
		}
		
		// check left
		if(move.getX2() > 1) {
			temp.clear();
			foundOtherEnd = false;
			
			for(int x = (move.getX2() - 1); x >= 0; x--) {
				if(board.getTile(x, move.getY2()) == moverPiece) {
					foundOtherEnd = true;
					break;
				}
				else if(board.getTile(x, move.getY2()) == oposerPiece) {
					temp.add(move.getX1() + "," + move.getY1());
				}
				else {
					break;
				}
			}
			
			if(foundOtherEnd) {
				caps.addAll(temp);
			}	
		}
		
		// check right
		if(move.getX2() < board.getWidth() - 1) {
			temp.clear();
			foundOtherEnd = false;
			
			for(int x = (move.getX2() + 1); x < board.getWidth(); x++) {
				if(board.getTile(x, move.getY2()) == moverPiece) {
					foundOtherEnd = true;
					break;
				}
				else if(board.getTile(x, move.getY2()) == oposerPiece) {
					temp.add(move.getX1() + "," + move.getY1());
				}
				else {
					break;
				}
			}
			
			if(foundOtherEnd) {
				caps.addAll(temp);
			}
		}
		
		return caps;
	}
	
	private void executeMove(GameMove move) {
		if(move.getX2() != GameMove.OFFBOARD && move.getY2() != GameMove.OFFBOARD) {
			System.out.println("one");
			board.setTile(move.getX2(), move.getY2(), move.getPiece());
		}
		
		if(move.getX1() != GameMove.OFFBOARD && move.getY1() != GameMove.OFFBOARD) {
			System.out.println("two");
			board.setTile(move.getX1(), move.getY1(), Piece.NONE);
		}
	}
	
	private boolean playerCanMove(Player player) {
		boolean result = false;
		
		int height = board.getHeight();
		int width = board.getWidth();
		
		for(int x = 0; x < width && !result; x++) {
			for(int y = 0; y < height && !result; y++) {
				Piece piece = board.getTile(x, y);
				
				if(piece == getPlayerPiece(player)) {
					result |= x > 0 && (board.getTile(x - 1, y) == Piece.NONE);
					result |= x < (width - 1) && (board.getTile(x + 1, y) == Piece.NONE);
					result |= y > 0 && (board.getTile(x, y - 1) == Piece.NONE);
					result |= y < (height - 1) && (board.getTile(x, y + 1) == Piece.NONE);
				}
			}
		}
		
		return result;
	}
}